/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package org.karol.lx.grader;

import java.io.IOException;
import java.net.InetAddress;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.params.HttpConnectionParams;
import org.apache.http.params.HttpParams;
import org.apache.http.protocol.BasicHttpContext;
import org.apache.http.util.EntityUtils;
import org.karol.lx.fetcher.RequestReceiver;
import org.karol.lx.model.GradeRequest;

/**
 *
 * @author karol
 */
public class StandardGraderInterface implements GraderEventListener {

    protected double mLoad = 0.0;
    private InetAddress mAddress;
    private int mState;
    private ArrayList<GradeRequest> mHandledRequest = new ArrayList<GradeRequest>();
    private boolean active = false;
    private String mDirectory;
    ///Constants
    public static final int PING_TIMEOUT = 10000;
    public static final int CONNECTION_TIMEOUT = 10000;
    public static final int SOCKET_TIMEOUT = 8000;
    public static final String TEST_PAGE_QUERY_STRING = "index.php/slave/evaluate/test";
    public static final String GRADE_QUERY_STRING = "index.php/slave/evaluate/grade";
    ///State
    public static final int STATE_OFFLINE = 0;
    public static final int STATE_INITIALIZING = 1;
    public static final int STATE_INITIALIZED = 2;
    public static final int STATE_FAILED = 3;
    public static final int STATE_TERMINATING = 4;
    private RequestReceiver mReceiver = null;

    public StandardGraderInterface(InetAddress pAddress , String pDirectory) {
        this.mAddress = pAddress;
        mDirectory = pDirectory;
    }

    public boolean isActive() {
        return active;
    }

    public void setActive(boolean pActive) {
        active = pActive;
    }

    public void setReceiver(RequestReceiver pReceiver) {
        mReceiver = pReceiver;
    }

    public InetAddress getAddress() {
        return mAddress;
    }

    public int getState() {
        return mState;
    }

    protected String getTestPageURL() {
        return "http://" + mAddress.getHostAddress() + "/" + mDirectory + "/" + TEST_PAGE_QUERY_STRING;
    }

    public ArrayList<GradeRequest> getHandledRequests() {
        return mHandledRequest;
    }

    public boolean isTestPageReachable() {
        HttpClient tClient = new DefaultHttpClient();
        HttpPost tMethod = new HttpPost(getTestPageURL());
        HttpParams tParams = tClient.getParams();
        HttpConnectionParams.setConnectionTimeout(tParams, CONNECTION_TIMEOUT);
        HttpConnectionParams.setSoTimeout(tParams, SOCKET_TIMEOUT);
        try {
            HttpResponse tResponse = tClient.execute(tMethod, new BasicHttpContext());
            HttpEntity tEntity = tResponse.getEntity();

            if (tResponse.getStatusLine().getStatusCode() == 200) { //Success
                return true;
            } else { //Failed, broken request
                return false;
            }
        } catch (IOException ex) { //Failed
            Logger.getLogger(StandardGraderInterface.class.getName()).log(Level.SEVERE, null, ex);
            return false;
        }
    }

    public boolean isReachable() {
        try {
            return mAddress.isReachable(PING_TIMEOUT);
        } catch (IOException ex) {
            Logger.getLogger(StandardGraderInterface.class.getName()).log(Level.SEVERE, null, ex);
            return false;
        }
    }

    public String getGraderUrl() {
        return "http://" + mAddress.getHostAddress() + "/" + mDirectory + "/" + GRADE_QUERY_STRING;
    }

    public boolean offerRequest(GradeRequest pRequest) {
        pRequest.m_DispatchTime = new Date(Calendar.getInstance().getTimeInMillis());
        mHandledRequest.add(pRequest);
        (new Thread(new GraderBackgroundWorker(getGraderUrl(), this, pRequest))).start();
        Logger.getLogger(StandardGraderInterface.class.getName()).log(Level.INFO, "Accepting {0} to {1} with load {2}, handled request count = {3}", new Object[]{pRequest, getGraderUrl(), getLoadPercentage(), mHandledRequest.size()});
        return true;
    }

    public synchronized void requeueHandledRequests() {
        for (GradeRequest g : mHandledRequest) {
            mReceiver.addRequest(g);
        }
        mHandledRequest.clear();
    }

    public synchronized double getActualLoadPercentage() {
        HttpClient tClient = new DefaultHttpClient();
        HttpPost tMethod = new HttpPost(getTestPageURL());
        HttpParams tParams = tClient.getParams();
        HttpConnectionParams.setConnectionTimeout(tParams, CONNECTION_TIMEOUT);
        HttpConnectionParams.setSoTimeout(tParams, SOCKET_TIMEOUT);
        try {
            HttpResponse tResponse = tClient.execute(tMethod, new BasicHttpContext());
            HttpEntity tEntity = tResponse.getEntity();

            if (tResponse.getStatusLine().getStatusCode() == 200) { //Success
                //return true;
                String tReturn = EntityUtils.toString(tEntity);
                String[] rows = tReturn.split("\n");
                return Double.valueOf(rows[0]);
            } else { //Failed, broken request
                return -1.00;
            }
        } catch (IOException ex) { //Failed
            //Logger.getLogger(GraderBackgroundWorker.class.getName()).log(Level.SEVERE, null, ex);
            return -1.00;
        }
    }

    public double getLoadPercentage() {
        return mLoad;
    }

    public void setLoadPercentage(double pLoad) {
        mLoad = pLoad;
    }

    @Override
    public void onGradingSuccess(GradeRequest pRequest) {
        Logger.getLogger(getClass().getName()).log(Level.INFO, "{0} successful", new Object[]{pRequest});
        mHandledRequest.remove(pRequest);
    }

    @Override
    public void onGradingFailed(GradeRequest pRequest, Object pReason, boolean disposable) {
        if (disposable) {
            Logger.getLogger(getClass().getName()).log(Level.WARNING, "{0} failed due {1} and disposable, disposing..", new Object[]{pRequest, pReason.toString()});
            pRequest.dispose();
        } else {
            Logger.getLogger(getClass().getName()).log(Level.WARNING, "{0} failed due {1} requeing..", new Object[]{pRequest, pReason.toString()});
            pRequest.requeue();
            mReceiver.addRequest(pRequest);
        }
        mHandledRequest.remove(pRequest);
    }
}
